#!/usr/bin/python

from hashdb_output import log, VERBOSE, QUIET, DEBUG, DEFAULT
from hashdb_config_base import *
from hashdb_config_defaults import *
from hashdb_version import HASHDB_VERSION
import platform
import argparse
import os

DatabaseView = namedtuple('DatabaseView', 'database path')

def parse_commandline(fparse):
    def func(text):
        try:
            return fparse(text)
        except ValueError, ex:
            raise argparse.ArgumentTypeError('%s' % ex.message)
    return func

def parse_config_commandline(settings=None, override=True):
    parser = argparse.ArgumentParser(
        prog='hashdb',
        fromfile_prefix_chars='@',
        description='%(prog)s is a tool for maintaining and querying a database of filepaths and their hash values. This allows for cached hash calculations as well as duplicate file detection')

    parser.add_argument('--version', action='version', version='%%(prog)s v%s' % HASHDB_VERSION)

    # config (verbosity)
    ##verbosity = parser.add_argument_group('verbosity')
    verbosity = parser.add_mutually_exclusive_group()
    verbosity.set_defaults(verbosity=None)
    verbosity.add_argument('-v', '--verbose', action='store_const', const=VERBOSE, dest='verbosity', help='provide verbose output')
    verbosity.add_argument('-q', '--quiet', action='store_const', const=QUIET, dest='verbosity', help='supress output')
    verbosity.add_argument('--debug', action='store_const', const=DEBUG, dest='verbosity', help='provide debugging output')
    verbosity.add_argument('--normal', action='store_const', const=DEFAULT, dest='verbosity', help='provide normal output')

    # config (hashdb config file)
    ##config = parser.add_argument_group('config')
    config = parser.add_mutually_exclusive_group()
    config.set_defaults(
        config=None,
        config_none=False)
    config.add_argument('--no-config', dest='config_none', action='store_true', help='do not use (any) config file')
    config.add_argument('--config', dest='config', type=parse_commandline(parse_text__filename), help='extract options from specified config file (commandline arguments take precedence)')

    # config (updatedb)
    parser.set_defaults(updatedb=None)
    if CONFIG_UPDATEDB != None:
        ##updatedb = parser.add_argument_group('updatedb')
        updatedb = parser.add_mutually_exclusive_group()
        updatedb.add_argument('--no-updatedb', dest='updatedb', action='store_false', help='do not import settings from updatedb config file')
        updatedb.add_argument('--use-updatedb', dest='updatedb', action='store_true', help='import settings from updatedb config file')

    # config (walking)
    ##walk = parser.add_argument_group('walk')
    walk = parser.add_mutually_exclusive_group()
    walk.set_defaults(
        walk_depth=None
        )
    walk.add_argument('--depth', dest='walk_depth', action='store_true',
        help='Perform depth-first walking of the directory tree.' + (' (default)' if CONFIG_WALK_DEPTH else ''))
    walk.add_argument('--breadth', dest='walk_depth', action='store_false',
        help='Perform breadth-first walking of the directory tree.' + (' (default)' if not CONFIG_WALK_DEPTH else ''))

    # config (database/s)
    database = parser.add_argument_group('database/s')
    database.set_defaults(
        database=None,
        databases_locals=[],
        databases_remotes=[],
    )
    database.add_argument('--database', type=parse_commandline(parse_text__filename), dest='database',
        help='Specify the primary database to operate on/with')

    # local
    class DatabaseLocalAction(argparse.Action):
        def __call__(self, parser, namespace, values, option_string=None):
            setattr(namespace, self.dest, (getattr(namespace, self.dest, []) or []) + [DatabaseLocal(target='//', database=values[0], subtree='//')])
    database.add_argument('--local', metavar="DATABASE", type=parse_commandline(parse_text__filename), dest='databases_locals', action=DatabaseLocalAction, nargs=1,
        help='Specify additional local database (databases rooted at the same location as the primary database)')
    class DatabaseLocalJoinAction(argparse.Action):
        def __call__(self, parser, namespace, values, option_string=None):
            setattr(namespace, self.dest, (getattr(namespace, self.dest, []) or []) + [DatabaseLocal(target=values[0], database=values[1], subtree=values[2])])
    database.add_argument('--join', '--local-join', metavar="X", type=parse_commandline(parse_text__filename), dest='databases_locals', action=DatabaseLocalJoinAction, nargs=3,
        help=\
        'LOCAL DATABASE SUBTREE '
        'Specify additional database to be joined with the primary database. ' +
        'LOCAL : target path where the database will be grafted. ' +
        'DATABASE : database to be joined with the primary database. ' +
        'SUBTREE : subtree to be grafted.'
    )

    class DatabaseRemoteAction(argparse.Action):
        def __call__(self, parser, namespace, values, option_string=None):
            setattr(namespace, self.dest, (getattr(namespace, self.dest, []) or []) + [DatabaseRemote(database=values[0], subtree='//')])
    database.add_argument('--remote', metavar="DATABASE", type=parse_commandline(parse_text__filename), dest='databases_remotes', action=DatabaseRemoteAction, nargs=1,
        help='Specify remote database which is not necessarily mounted on this system')
    class DatabaseRemoteSubtreeAction(argparse.Action):
        def __call__(self, parser, namespace, values, option_string=None):
            setattr(namespace, self.dest, (getattr(namespace, self.dest, []) or []) + [DatabaseRemote(*values)])
    database.add_argument('--remote-subtree', metavar="X", type=parse_commandline(parse_text__filename), dest='databases_remotes', action=DatabaseRemoteSubtreeAction, nargs=2,
        help='DATABASE SUBTREE Specify remote database which is not necessarily mounted on this system, including only the subtree specified by PATH')


    # config (skipers)
    skip = parser.add_argument_group('skip')
    skip.set_defaults(
        skip_mounts=None,
        skip_binds=None,
        skip_symlinks=None,

        skip_fstypes=[],

        skip_names=[],
        skip_paths=[],
        skip_dirnames=[],
        skip_filenames=[],
    )
    # skip mounts
    group = skip.add_mutually_exclusive_group()
    group.add_argument('--skip-mounts', dest='skip_mounts', action='store_true',
        help='Do not cross mount points.' + (' (default)' if CONFIG_SKIP_MOUNTS else ''))
    group.add_argument('--keep-mounts', dest='skip_mounts', action='store_false',
        help='Allow crossing of mount points.' + (' (default)' if not CONFIG_SKIP_MOUNTS else ''))

    # skip binds
    group = skip.add_mutually_exclusive_group()
    group.add_argument('--skip-binds', dest='skip_binds', action='store_true',
        help='Do not cross into bind mounts.' + (' (default)' if CONFIG_SKIP_BINDS else ''))
    group.add_argument('--keep-binds', dest='skip_binds', action='store_false',
        help='Allow crossing into bind mounts.' + (' (default)' if not CONFIG_SKIP_BINDS else ''))

    # skip symlinks
    group = skip.add_mutually_exclusive_group()
    group.add_argument('--skip-symlinks', dest='skip_symlinks', action='store_true',
        help='Do not resolve/traverse symlinks.' + (' (default)' if CONFIG_SKIP_SYMLINKS else ''))
    group.add_argument('--keep-symlinks', dest='skip_symlinks', action='store_false',
        help='Resolve/Traverse symlinks.' + (' (default)' if not CONFIG_SKIP_SYMLINKS else ''))

    # skip fstype
    skip.add_argument('--skip-fstype', dest='skip_fstypes', action='append', metavar='FSTYPE',
        help='Do not cross into mounts using the with the specified file system. (May contain globbing characters)')

    # skip name
    skip.add_argument('--skip-name', dest='skip_names', action='append', metavar='NAME',
        help='Skip paths containing the specified base name. (May contain globbing characters)')

    # skip dirname
    skip.add_argument('--skip-dirname', dest='skip_dirnames', action='append', metavar='DIRNAME',
        help='Skip directories containing the specified base name. (May contain globbing characters)')

    # skip filename
    skip.add_argument('--skip-filename', dest='skip_filenames', action='append', metavar='FILENAME',
        help='Skip filepaths containing the specified base name. (May contain globbing characters)')

    # skip path
    skip.add_argument('--skip-path', dest='skip_paths', action='append', metavar='PATH',
        help='Skip the given path. (May contain globbing characters)')

    # config subcommands
    subparser = parser.add_subparsers(
        title='subcommands',
        description='Use "%(prog)s <subcommand> --help" for help on a given subcommand')

    # config (subcommand: hash)
    cmd_hash = subparser.add_parser('hash',
        help='Perform a hash on all specified TARGETs')
    cmd_hash.set_defaults(
        cmd='hash',
        hash_definitive=None,
        hash_force=None,
        hash_targets=[])

    # config (subcommand: hash) (hash_definitive)
    group = cmd_hash.add_mutually_exclusive_group()
    group.add_argument('--not-definitive', dest='hash_definitive', action='store_false',
        help='TARGETs are only updated, no targets are removed.' + (' (default)' if not CONFIG_HASH_DEFINITIVE else ''))
    group.add_argument('--definitive', dest='hash_definitive', action='store_true',
        help='Any targets stored in the primary database which are not accessible via TARGETs will be discarded.' + (' (default)' if CONFIG_HASH_DEFINITIVE else ''))

    # config (subcommand: hash) (hash_force)
    group = cmd_hash.add_mutually_exclusive_group()
    group.add_argument('--no-force', dest='hash_force', action='store_false',
        help='Only rehash TARGETs as required')
    group.add_argument('--force', dest='hash_force', action='store_true',
        help='Force re-hash of all TARGETs')

    # config (subcommand: hash) (targets)
    cmd_hash.add_argument('hash_targets', metavar='TARGET', nargs='*',
        help='List of target files and folders to hash')

    # config (subcommand: match)
    cmd_match = subparser.add_parser('match',
        help='Find duplicates of any files specifed via TARGETs')
    cmd_match.set_defaults(
        cmd='match',
        match_verify=None,
        match_targets=[])

    # config (subcommand: match) (match_verify)
    group = cmd_match.add_mutually_exclusive_group()
    group.add_argument('--verify', dest='match_verify', action='store_true',
        help='Check to see if duplicate files are still accesssible on the system (does not apply to remote databases).' + (' (default)' if CONFIG_MATCH_VERIFY else ''))
    group.add_argument('--no-verification', dest='match_check', action='store_false',
        help='Do not check to see if duplicate files are still accesssible on the system.' + (' (default)' if not CONFIG_MATCH_VERIFY else ''))

    # config (subcommand: match) (targets)
    cmd_match.add_argument('match_targets', metavar='TARGET', nargs='*',
        help='List of target files and folders to match against')

    # config (subcommand: view)
    cmd_view = subparser.add_parser('view',
        help='Display everything in the database matching TARGETs')
    cmd_view.set_defaults(
        cmd='view',
        view_locals=[],
        view_remotes=[],
        view_databases=[],
        view_targets=[])

    # config (subcommand: view) (view_locals)
    cmd_view.add_argument('--local', dest='view_locals', metavar='TARGET', action='append',
        help='Add target, filtered against all local databases (prime, local, joins).')
    # config (subcommand: view) (view_remotes)
    cmd_view.add_argument('--remote', dest='view_remotes', metavar='TARGET', action='append',
        help='Add target, filtered against remote databases. (May contain globbing characters)')
    class DatabaseViewAction(argparse.Action):
        def __call__(self, parser, namespace, values, option_string=None):
            setattr(namespace, self.dest, (getattr(namespace, self.dest, []) or []) + [DatabaseView(*values)])
    # config (subcommand: view) (view_databases)
    cmd_view.add_argument('--database', dest='view_databases', metavar='X', action='append', nargs=2,
        help='DATABASE TARGET Add target, filtered against the specified databases. (May contain globbing characters)')

    # config (subcommand: view) (targets)
    cmd_match.add_argument('view_targets', metavar='TARGET', nargs='*',
        help='List of targets to view')

    # config (subcommand: query)
    cmd_query = subparser.add_parser('query',
        help='Execute an SQL command against the database')
    cmd_query.set_defaults(
        cmd='query',
        query_sql=CONFIG_QUERY_SQL,
        query_sql_from_file=None
    )
    group = cmd_query.add_mutually_exclusive_group(required=True)
    def readinput(value):
        value = argparse.FileType('rt')(value)
        value = value.read()
        return value
    group.add_argument('--from-file', metavar='FILENAME', type=readinput, dest='query_sql_from_file',
        help='Gather sql query from file. (Use "-" to read from stdin)')
    class QuerySQLAction(argparse.Action):
        def __call__(self, parser, namespace, values, option_string=None):
            setattr(namespace, self.dest, ' '.join(values))
    group.add_argument('query_sql', metavar='SQL', nargs='*', default=[], action=QuerySQLAction,
        help='All SQL arguments will be joined together and executed as one statement')


    # config (subcommand: schema)
    cmd_query = subparser.add_parser('schema',
        help='Display the schema in use for this command. (Includes all the primary, local, remote and joined databases)')
    cmd_query.set_defaults(
        cmd='schema')


    # parse args
    args = parser.parse_args()
    ##print args

    # extract final settings
    if settings == None:
        settings = ConfigSettings()

    # config (verbosity)
    if args.verbosity != None:
        settings.set('verbosity',args.verbosity, override)

    # config (hashdb config file)
    if args.config_none == True:
        settings.set('config', [], override)
    elif args.config != None:
        settings.add_config(args.config, override)

    # config (updatedb)
    if args.updatedb != None:
        settings.set('updatedb', args.updatedb, override)

    # config (walking)
    if args.walk_depth != None:
        settings.set('walk_depth', args.walk_depth, override)


    # config (database/s)
    if args.database != None:
        settings.set('database', args.database, override)
    if len(args.databases_locals) != 0:
        settings.extend_databases_locals(args.databases_locals, override)
    if len(args.databases_remotes) != 0:
        settings.extend_databases_remotes(args.databases_remotes, override)

    # config (skipers)
    if args.skip_mounts != None:
        settings.set('skip_mounts', args.skip_mounts, override)
    if args.skip_binds != None:
        settings.set('skip_binds', args.skip_binds, override)
    if args.skip_symlinks != None:
        settings.set('skip_symlinks', args.skip_symlinks, override)

    if len(args.skip_fstypes) != 0:
        settings.extend_skip_fstypes(args.skip_fstypes, override)

    if len(args.skip_names) != 0:
        settings.extend_skip_names(args.skip_names, override)
    if len(args.skip_paths) != 0:
        settings.extend_skip_paths(args.skip_paths, override)
    if len(args.skip_dirnames) != 0:
        settings.extend_skip_dirnames(args.skip_dirnames, override)
    if len(args.skip_filenames) != 0:
        settings.extend_skip_filenames(args.skip_filenames, override)

    # config subcommands
    if args.cmd == 'hash':
        # config (subcommand: hash)
        settings.set('cmd', 'hash', override)

        # config (subcommand: hash) (hash_definitive)
        if args.hash_definitive != None:
            settings.set('hash_definitive', args.hash_definitive, override)

        # config (subcommand: hash) (hash_force)
        if args.hash_force != None:
            settings.set('hash_force', args.hash_force, override)

        # config (subcommand: hash) (hash_targets)
        if len(args.hash_targets) != 0:
            settings.extend_hash_targets(args.hash_targets, override)
    elif args.cmd == 'match':
        # config (subcommand: match)
        settings.set('cmd', 'match', override)

        # config (subcommand: match) (match_verify)
        if args.match_verify != None:
            settings.set('match_verify', args.match_verify, override)

        # config (subcommand: match) (match_targets)
        if len(args.match_targets) != 0:
            settings.extend_match_targets(args.match_targets, override)
    elif args.cmd == 'view':
        # config (subcommand: view)
        settings.set('cmd', 'view', override)
        # config (subcommand: view) (view_locals)
        if len(args.view_locals) != 0:
            settings.extend_view_locals(args.view_locals, override)
        # config (subcommand: view) (view_remotes)
        if len(args.view_remotes) != 0:
            settings.extend_view_remotes(args.view_remotes, override)
        # config (subcommand: view) (view_databases)
        if len(args.view_databases) != 0:
            settings.extend_view_databases(args.view_databases, override)
        # config (subcommand: view) (view_targets)
        if len(args.view_targets) != 0:
            settings.extend_view_targets(args.view_targets, override)
    elif args.cmd == 'query':
        # config (subcommand: query)
        settings.set('cmd', 'query', override)
        if args.query_sql_from_file != None:
            settings.set('query_sql', args.query_sql_from_file, override)
        else:
            settings.set('query_sql', args.query_sql, override)
    elif args.cmd == 'schema':
        # config (subcommand: schema)
        settings.set('cmd', 'schema', override)

    return settings

if __name__ == '__main__':
    print parse_config_commandline()